package cn.melonlib.record.oper.comp.interceptor;

import cn.hutool.core.bean.BeanUtil;
import cn.melonlib.cores.aop.comp.web.WebInfo;
import cn.melonlib.cores.commons.model.entity.IEntity;
import cn.melonlib.cores.identification.comp.messenger.IdentificationMessenger;
import cn.melonlib.cores.identification.model.entity.Identification;
import cn.melonlib.cores.identification.model.entity.Organization;
import cn.melonlib.cores.jpa.model.bean.collections.EntityMap;
import cn.melonlib.cores.jpa.model.bean.collections.JsonMap;
import cn.melonlib.cores.model.anno.WorkerQuery;
import cn.melonlib.cores.model.anno.WorkerService;
import cn.melonlib.cores.model.anno.WorkerStore;
import cn.melonlib.record.oper.anno.OperatorRecordService;
import cn.melonlib.record.oper.anno.RecordMethod;
import cn.melonlib.record.oper.comp.service.OperationRecordSaveService;
import cn.melonlib.record.oper.enums.OperatorRecordMode;
import cn.melonlib.record.oper.model.entity.OperationRecord;
import cn.melonlib.record.oper.model.enums.RecordType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang3.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Component
public class OperatorRecordInterceptor extends ApplicationObjectSupport implements MethodInterceptor {


    @Nullable
    @Override
    public Object invoke(@NotNull MethodInvocation invocation) throws Throwable {
        Method method=invocation.getMethod();
        Class<?>targetClass= AopUtils.getTargetClass(invocation.getThis());
        if(validMethodRecord(targetClass,method)){
            long start=System.currentTimeMillis();

            OperationRecordSaveService operationRecordSaveService=getApplicationContext().getBean(OperationRecordSaveService.class);
            IdentificationMessenger identificationMessenger=getApplicationContext().getBean(IdentificationMessenger.class);
            WebInfo webInfo=getApplicationContext().getBean(WebInfo.class);
            ObjectMapper jsonMapper=getApplicationContext().getBean(ObjectMapper.class);
            List<Object> params=Arrays.stream(invocation.getArguments()).collect(Collectors.toList());
            OperationRecord operationRecord=new OperationRecord();
            StringBuilder methodSign=new StringBuilder(targetClass.getName())
                    .append("#").append(method.getName()).append("(")
                    .append(paramsSign(method)).append("):").append(retuenTypeSign(method.getReturnType()));
            operationRecord.setMethod(methodSign.toString());
            if(AnnotationUtils.findAnnotation(method,WorkerQuery.class)!=null){
                operationRecord.setRecordType(RecordType.SEARCH);
            }else if(AnnotationUtils.findAnnotation(method,WorkerStore.class)!=null){
                operationRecord.setRecordType(RecordType.STORE);
            }else{
                operationRecord.setRecordType(RecordType.INFO);
            }
            String paramsVal=jsonMapper.writeValueAsString( Arrays.stream(method.getParameters()).reduce(new ArrayList<JsonMap<Object>>(),(prms, param)->{
                JsonMap<Object> prmItem=JsonMap.createMap();
                prmItem.append(param.getName(),transParam(param,params.get(prms.size())));
                prms.add(prmItem);
                return prms;
            },(i,p)->null));
            Identification<?> identification=identificationMessenger.getIdentification();
            Organization<?> organization=identificationMessenger.getOrganization();
            operationRecord.setParams(paramsVal);
            WebInfo.ClientInfo clientInfo=webInfo.getClientInfo();
            operationRecord.setExtendsInfo(jsonMapper.writeValueAsString(clientInfo));
            operationRecord.setStartTime(start);
            operationRecord.setCreatorId(identification.getOpenid());
            operationRecord.setCreateDateTime(System.currentTimeMillis());
            operationRecord.setTenantId(ObjectUtils.defaultIfNull(organization.getId(),"").toString());
            operationRecord.setTenantCode(organization.getCode());
            operationRecord.setTenantName(organization.getName());
            operationRecord.setCreatorCode(identification.getOpenid());
            operationRecord.setCreatorName(BeanUtil.getProperty(identification,"name"));
            operationRecord.setRemoved(false);
            Object result=invocation.proceed();
            long end=System.currentTimeMillis();
            operationRecord.setEndTime(end);
            long time=end-start;
            operationRecord.setConsuming(time);
            operationRecord.setResult(jsonMapper.writeValueAsString(returnVal(result)));
            operationRecordSaveService.saveRecord(operationRecord);
            return result;
        }else{
            return invocation.proceed();
        }

    }


    private String paramsSign(Method method){
        return Arrays.stream(method.getParameters()).map(parameter -> {
            return new StringBuilder(parameter.getName())
                    .append(":").append(parameter.getType().getName());
        }).collect(Collectors.joining(","));
    }

    private String retuenTypeSign(Class<?> cls){
        if(Objects.equals(cls,Void.TYPE)){
            return "void";
        }
        return cls.getName();
    }

    private Object transParam(Parameter parameter,Object param){
        try {
            if (IEntity.class.isAssignableFrom(parameter.getType())) {
                return new EntityMap<Object, IEntity<? extends Serializable>>((IEntity<? extends Serializable>) param);
            } else if (Map.class.isAssignableFrom(parameter.getType())) {
                return param;
            } else if (parameter.getType().isPrimitive()) {
                return param;
            } else if (Collection.class.isAssignableFrom(parameter.getType())) {
                return ((Collection) param).stream().map(item -> transParam(parameter, item)).collect(Collectors.toList());
            } else {
                if (param == null) {
                    return null;
                }
                return param.toString();
            }
        }catch (Exception e){
            if (param == null) {
                return null;
            }
            return param.toString();
        }
    }
    private Object returnVal(Object param){
        try {
            if (param instanceof IEntity) {
                return new EntityMap<Object, IEntity<? extends Serializable>>((IEntity<? extends Serializable>) param);
            } else if (param instanceof Map) {
                return param;
            } else if (param.getClass().isPrimitive()) {
                return param;
            } else if (param instanceof Collection) {
                return ((Collection) param).stream().map(item -> returnVal(item)).collect(Collectors.toList());
            } else {
                if (param == null) {
                    return null;
                }
                return param.toString();
            }
        }catch (Exception e){
            if (param == null) {
                return null;
            }
            return param.toString();
        }
    }

    public static boolean validMethodRecord(Class<?> targetClass,Method method){
        OperatorRecordService operatorRecordService=AnnotationUtils.findAnnotation(targetClass, OperatorRecordService.class);
        if(operatorRecordService==null){
            return false;
        }
        RecordMethod recordMethod=AnnotationUtils.findAnnotation(method,RecordMethod.class);
        if(recordMethod!=null){
            return true;
        }
        String methodName=method.getName();
        if(Arrays.stream(operatorRecordService.excludes()).anyMatch(regex-> Pattern.compile(regex).matcher(methodName).matches())){
            return false;
        };
        if(Arrays.stream(operatorRecordService.includes()).anyMatch(regex->Pattern.compile(regex).matcher(methodName).matches())){
            return true;
        }
        OperatorRecordMode mode=operatorRecordService.mode();
        switch (mode){
            case NONE:return false;
            case ALL:return true;
            case QUERY:return AnnotationUtils.findAnnotation(targetClass, WorkerService.class)!=null
                    &&AnnotationUtils.findAnnotation(method, WorkerQuery.class)!=null;
            case STORE:return AnnotationUtils.findAnnotation(targetClass, WorkerService.class)!=null
                    &&AnnotationUtils.findAnnotation(method, WorkerStore.class)!=null;
            case WORKER:return AnnotationUtils.findAnnotation(targetClass, WorkerService.class)!=null&&
                    (AnnotationUtils.findAnnotation(method, WorkerQuery.class)!=null
                    ||AnnotationUtils.findAnnotation(method, WorkerStore.class)!=null);
            default:return false;
        }
    }
}
